home *** CD-ROM | disk | FTP | other *** search
/ Australian Personal Computer 2000 July / CD 3 / redhat-6.2.iso / RedHat / instimage / usr / lib / anaconda / gettext_rh.py < prev    next >
Encoding:
Python Source  |  2000-03-08  |  9.4 KB  |  358 lines

  1. """This module allows python programs to use GNU gettext message catalogs.
  2.  
  3. Modified by Red Hat, Inc for use with anaconda installer.
  4. Original file information follows:
  5.  
  6. Author: James Henstridge <james@daa.com.au>
  7. (This is loosely based on gettext.pl in the GNU gettext distribution)
  8.  
  9. The best way to use it is like so:
  10.     import gettext
  11.     gettext.bindtextdomain(PACKAGE, LOCALEDIR)
  12.     gettext.textdomain(PACKAGE)
  13.     _ = gettext.gettext
  14.     print _('Hello World')
  15.  
  16. where PACKAGE is the domain for this package, and LOCALEDIR is usually
  17. '$prefix/share/locale' where $prefix is the install prefix.
  18.  
  19. If you have more than one catalog to use, you can directly create catalog
  20. objects.  These objects are created as so:
  21.     import gettext
  22.     cat = gettext.Catalog(PACKAGE, localedir=LOCALEDIR)
  23.     _ = cat.gettext
  24.     print _('Hello World')
  25.  
  26. The catalog object can also be accessed as a dictionary (ie cat['hello']).
  27.  
  28. There are also some experimental features.  You can add to the catalog, just
  29. as you would with a normal dictionary.  When you are finished, you can call
  30. its save method, which will create a new .mo file containing all the
  31. translations:
  32.     import gettext
  33.     cat = Catalog()
  34.     cat['Hello'] = 'konichiwa'
  35.     cat.save('./tmp.mo')
  36.  
  37. Once you have written an internationalized program, you can create a .po file
  38. for it with "xgettext --keyword=_ fillename ...".  Then do the translation and
  39. compile it into a .mo file, ready for use with this module.  Note that you
  40. will have to use C style strings (ie. use double quotes) for proper string
  41. extraction.
  42. """
  43. import os, string, iutil, gzread
  44.  
  45. prefix = '/usr/local'
  46. localedir = prefix + '/share/locale'
  47.  
  48. def _expandLang(str):
  49.     langs = [str]
  50.     # remove charset ...
  51.     if '.' in str:
  52.         langs.append(string.split(str, '.')[0])
  53.     # also add 2 character language code ...
  54.     if len(str) > 2:
  55.         langs.append(str[:2])
  56.     return langs
  57.  
  58. lang = []
  59. for env in 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG':
  60.     if os.environ.has_key(env):
  61.         lang = string.split(os.environ[env], ':')
  62.         lang = map(_expandLang, lang)
  63.         lang = reduce(lambda a, b: a + b, lang)
  64.         break
  65. if 'C' not in lang:
  66.     lang.append('C')
  67.  
  68. # remove duplicates
  69. i = 0
  70. while i < len(lang):
  71.     j = i + 1
  72.     while j < len(lang):
  73.         if lang[i] == lang[j]:
  74.             del lang[j]
  75.         else:
  76.             j = j + 1
  77.     i = i + 1
  78. del i, j
  79.  
  80. if os.environ.has_key('PY_XGETTEXT'):
  81.     xgettext = os.environ['PY_XGETTEXT']
  82. else:
  83.     xgettext = None
  84.  
  85. if iutil.getArch() == 'sparc':
  86.     _gettext_byteorder = 'msb'
  87. else:
  88.     _gettext_byteorder = 'lsb'
  89.  
  90. del os, string, iutil
  91.  
  92. error = 'gettext.error'
  93.  
  94. def _lsbStrToInt(str):
  95.     return ord(str[0]) + \
  96.            (ord(str[1]) << 8) + \
  97.            (ord(str[2]) << 16) + \
  98.            (ord(str[3]) << 24)
  99. def _intToLsbStr(int):
  100.     return chr(int         & 0xff) + \
  101.            chr((int >> 8)  & 0xff) + \
  102.            chr((int >> 16) & 0xff) + \
  103.            chr((int >> 24) & 0xff)
  104. def _msbStrToInt(str):
  105.     return ord(str[3]) + \
  106.            (ord(str[2]) << 8) + \
  107.            (ord(str[1]) << 16) + \
  108.            (ord(str[0]) << 24)
  109. def _intToMsbStr(int):
  110.     return chr((int >> 24) & 0xff) + \
  111.            chr((int >> 16) & 0xff) + \
  112.            chr((int >> 8) & 0xff) + \
  113.            chr(int & 0xff)
  114. def _StrToInt(str):
  115.     if _gettext_byteorder == 'msb':
  116.         return _msbStrToInt(str)
  117.     else:
  118.         return _lsbStrToInt(str)
  119. def _intToStr(int):
  120.     if _gettext_byteorder == 'msb':
  121.         return _intToMsbStr(str)
  122.     else:
  123.         return _intToLsbStr(str)
  124.  
  125. def _getpos(levels = 0):
  126.     """Returns the position in the code where the function was called.
  127.     The function uses some knowledge about python stack frames."""
  128.     import sys
  129.     # get access to the stack frame by generating an exception.
  130.     try:
  131.         raise RuntimeError
  132.     except RuntimeError:
  133.         frame = sys.exc_traceback.tb_frame
  134.     frame = frame.f_back # caller's frame
  135.     while levels > 0:
  136.         frame = frame.f_back
  137.         levels = levels - 1
  138.     return (frame.f_globals['__name__'],
  139.         frame.f_code.co_name,
  140.         frame.f_lineno)
  141.  
  142. class Catalog:
  143.     def __init__(self, domain=None, localedir=localedir):
  144.         self.domain = domain
  145.         self.localedir = localedir
  146.         self.cat = {}
  147.         if not domain: return
  148.         for self.lang in lang:
  149.             if self.lang == 'C':
  150.                 return
  151.             catalog = "%s/%s/LC_MESSAGES/%s.mo" % (
  152.                 localedir, self.lang, domain)
  153.             try:
  154.                 f = gzread.open(catalog)
  155.                 buffer = f.read()
  156.                 f.close()
  157.                 del f
  158.                 break
  159.             except IOError:
  160.                 pass
  161.         else:
  162.             return # assume C locale
  163.  
  164.         if _StrToInt(buffer[:4]) != 0x950412de:
  165.             # magic number doesn't match
  166.             raise error, 'Bad magic number in %s' % (catalog,)
  167.  
  168.         self.revision = _StrToInt(buffer[4:8])
  169.         nstrings = _StrToInt(buffer[8:12])
  170.         origTabOffset  = _StrToInt(buffer[12:16])
  171.         transTabOffset = _StrToInt(buffer[16:20])
  172.         for i in range(nstrings):
  173.             origLength = _StrToInt(buffer[origTabOffset:
  174.                               origTabOffset+4])
  175.             origOffset = _StrToInt(buffer[origTabOffset+4:
  176.                               origTabOffset+8])
  177.             origTabOffset = origTabOffset + 8
  178.             origStr = buffer[origOffset:origOffset+origLength]
  179.         
  180.             transLength = _StrToInt(buffer[transTabOffset:
  181.                                transTabOffset+4])
  182.             transOffset = _StrToInt(buffer[transTabOffset+4:
  183.                                transTabOffset+8])
  184.             transTabOffset = transTabOffset + 8
  185.             transStr = buffer[transOffset:transOffset+transLength]
  186.             
  187.             self.cat[origStr] = transStr
  188.  
  189.     def gettext(self, string):
  190.         """Get the translation of a given string"""
  191.         if self.cat.has_key(string):
  192.             return self.cat[string]
  193.         else:
  194.             return string
  195.     # allow catalog access as cat(str) and cat[str] and cat.gettext(str)
  196.     __getitem__ = gettext
  197.     __call__ = gettext
  198.  
  199.     # this is experimental code for producing mo files from Catalog objects
  200.     def __setitem__(self, string, trans):
  201.         """Set the translation of a given string"""
  202.         self.cat[string] = trans
  203.     def save(self, file):
  204.         """Create a .mo file from a Catalog object"""
  205.         try:
  206.             f = open(file, "wb")
  207.         except IOError:
  208.             raise error, "can't open " + file + " for writing"
  209.         f.write(_intToStr(0x950412de))    # magic number
  210.         f.write(_intToStr(0))             # revision
  211.         f.write(_intToStr(len(self.cat))) # nstrings
  212.  
  213.         oIndex = []; oData = ''
  214.         tIndex = []; tData = ''
  215.         for orig, trans in self.cat.items():
  216.             oIndex.append((len(orig), len(oData)))
  217.             oData = oData + orig + '\0'
  218.             tIndex.append((len(trans), len(tData)))
  219.             tData = tData + trans + '\0'
  220.         oIndexOfs = 20
  221.         tIndexOfs = oIndexOfs + 8 * len(oIndex)
  222.         oDataOfs = tIndexOfs + 8 * len(tIndex)
  223.         tDataOfs = oDataOfs + len(oData)
  224.         f.write(_intToStr(oIndexOfs))
  225.         f.write(_intToStr(tIndexOfs))
  226.         for length, offset in oIndex:
  227.             f.write(_intToStr(length))
  228.             f.write(_intToStr(offset + oDataOfs))
  229.         for length, offset in tIndex:
  230.             f.write(_intToStr(length))
  231.             f.write(_intToStr(offset + tDataOfs))
  232.         f.write(oData)
  233.         f.write(tData)
  234.  
  235. _cat = None
  236. _cats = {}
  237.  
  238. if xgettext:
  239.     class Catalog:
  240.         def __init__(self, domain, localedir):
  241.             self.domain = domain
  242.             self.localedir = localedir
  243.             self._strings = {}
  244.         def gettext(self, string):
  245.             # there is always one level of redirection for calls
  246.             # to this function
  247.             pos = _getpos(2) # get this function's caller
  248.             if self._strings.has_key(string):
  249.                 if pos not in self._strings[string]:
  250.                     self._strings[string].append(pos)
  251.             else:
  252.                 self._strings[string] = [pos]
  253.             return string
  254.         __getitem__ = gettext
  255.         __call__ = gettext
  256.         def __setitem__(self, item, data):
  257.             pass
  258.         def save(self, file):
  259.             pass
  260.         def output(self, fp):
  261.             import string
  262.             fp.write('# POT file for domain %s\n' % (self.domain,))
  263.             for str in self._strings.keys():
  264.                 pos = map(lambda x: "%s(%s):%d" % x,
  265.                       self._strings[str])
  266.                 pos.sort()
  267.                 length = 80
  268.                 for p in pos:
  269.                     if length + len(p) > 74:
  270.                         fp.write('\n#:')
  271.                         length = 2
  272.                     fp.write(' ')
  273.                     fp.write(p)
  274.                     length = length + 1 + len(p)
  275.                 fp.write('\n')
  276.                 if '\n' in str:
  277.                     fp.write('msgid ""\n')
  278.                     lines = string.split(str, '\n')
  279.                     lines = map(lambda x:
  280.                             '"%s\\n"\n' % (x,),
  281.                             lines[:-1]) + \
  282.                             ['"%s"\n' % (lines[-1],)]
  283.                     fp.writelines(lines)
  284.                 else:
  285.                     fp.write('msgid "%s"\n' % (str,))
  286.                 fp.write('msgstr ""\n')
  287.                 
  288.     import sys
  289.     if hasattr(sys, 'exitfunc'):
  290.         _exitchain = sys.exitfunc
  291.     else:
  292.         _exitchain = None
  293.     def exitfunc(dir=xgettext, _exitchain=_exitchain):
  294.         # actually output all the .pot files.
  295.         import os
  296.         for file in _cats.keys():
  297.             fp = open(os.path.join(dir, file + '.pot'), 'w')
  298.             cat = _cats[file]
  299.             cat.output(fp)
  300.             fp.close()
  301.         if _exitchain: _exitchain()
  302.     sys.exitfunc = exitfunc
  303.     del sys, exitfunc, _exitchain, xgettext
  304.  
  305. def bindtextdomain(domain, localedir=localedir):
  306.     global _cat
  307.     if not _cats.has_key(domain):
  308.         _cats[domain] = Catalog(domain, localedir)
  309.     if not _cat: _cat = _cats[domain]
  310.  
  311. def textdomain(domain):
  312.     global _cat
  313.     if not _cats.has_key(domain):
  314.         _cats[domain] = Catalog(domain)
  315.     _cat = _cats[domain]
  316.  
  317. def gettext(string):
  318.     if _cat == None: raise error, "No catalog loaded"
  319.     return _cat.gettext(string)
  320.  
  321. _ = gettext
  322.  
  323. def dgettext(domain, string):
  324.     if domain is None:
  325.         return gettext(string)
  326.     if not _cats.has_key(domain):
  327.         raise error, "Domain '" + domain + "' not loaded"
  328.     return _cats[domain].gettext(string)
  329.  
  330. def test():
  331.     import sys
  332.     global localedir
  333.     if len(sys.argv) not in (2, 3):
  334.         print "Usage: %s DOMAIN [LOCALEDIR]" % (sys.argv[0],)
  335.         sys.exit(1)
  336.     domain = sys.argv[1]
  337.     if len(sys.argv) == 3:
  338.         bindtextdomain(domain, sys.argv[2])
  339.     textdomain(domain)
  340.     info = gettext('')  # this is where special info is often stored
  341.     if info:
  342.         print "Info for domain %s, lang %s." % (domain, _cat.lang)
  343.         print info
  344.     else:
  345.         print "No info given in mo file."
  346.  
  347. def getlangs():
  348.     global lang
  349.     return lang
  350.  
  351. def setlangs(newlang):
  352.     global lang
  353.     lang = newlang
  354.  
  355. if __name__ == '__main__':
  356.     test()
  357.  
  358.